using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Web;
using System.Web.Mvc;
using SignalR.Hubs;

namespace Mvc3SignalR.Models
{
    public class KnockoutHub: Hub
    {
        public void Initialize()
        {
            foreach (var prop in GetObservableProperties())
            {
                prop.SetValue(this, prop.GetValue(this, null), null);
            }
        }

        public void Set(string propertyName, object value)
        {
            PropertyInfo prop = this.GetType().GetProperty(propertyName);
            if (prop != null)
            {
                prop.SetValue(this, value, null);
            }
        }
        
        public object Get(string propertyName)
        {
            PropertyInfo prop = this.GetType().GetProperty(propertyName);
            if (prop != null)
            {
                return prop.GetValue(this, null);
            }
            return null;
        }

        public IHtmlString GetViewModel(string name, string domElementToBind = null)
        {
            string bindingElementString = domElementToBind == null ? string.Empty : ", " + domElementToBind;
            string result =String.Format(@"
            {0} = $.connection.{1};
            {3}
            {4}
            $.connection.hub.start(function () {{
                {0}.initialize();       
                ko.applyBindings({0}{2});
            }});",
                                         name, this.GetHubName(), bindingElementString, this.GetObservables(name), this.GetFunctions(name), string.Empty);
            return new MvcHtmlString(result);
        }

        private string GetHubName()
        {
            return LowerFirstLetter(this.GetType().Name);
        }

        public string GetObservables(string name)
        {
            string vmFormat = @"
            {0}.{1} = ko.observable('{2}');
            {0}.{1}.subscribe(function (newValue) {{
                {0}.set('{1}', newValue);
            }});";
            return GetObservableProperties()
                .Aggregate("", (current, observableProperty) =>
                               current +
                               String.Format(vmFormat, name, observableProperty.Name,
                                             observableProperty.GetValue(this, null)));
        }
//        public string GetComputed(string name)
//        {
//            string vmFormat = @"
//            {0}.{1} = ko.computed(function () {{
//                return {0}.get('{1}');
//            }}, {0});";
//            return GetComputedProperties()
//                .Aggregate("", (current, observableProperty) =>
//                               current +
//                               String.Format(vmFormat, name, observableProperty.Name,
//                                             LowerFirstLetter(observableProperty.Name)));
//        }

        public string GetFunctions(string name)
        {
            string vmFormat = @"
            {0}.{1} = function () {{
                {0}.{2}.call({0});
            }};";
            return Enumerable.Aggregate(this.GetClientFunctions(), "", (current, method) =>
                                                                       current +
                                                                       String.Format(vmFormat, name, method.Name, LowerFirstLetter(method.Name)));
        }

        private string LowerFirstLetter(string input)
        {
            var name = input.First().ToString().ToLower() + String.Join("", input.Skip(1));
            return name;
        }

        private IEnumerable<MethodInfo> GetClientFunctions()
        {
            return this.GetType().GetMethods().Where(prop => Attribute.IsDefined(prop, typeof(ClientFunctionAttribute)));
        }


        private IEnumerable<PropertyInfo> GetObservableProperties()
        {
            return this.GetType().GetProperties().Where(prop => Attribute.IsDefined((MemberInfo)prop, typeof(ObservableAttribute)));
        }


        //private IEnumerable<PropertyInfo> GetComputedProperties()
        //{
        //    return this.GetType().GetProperties().Where(prop => Attribute.IsDefined((MemberInfo)prop, typeof(ComputedObservableAttribute)));
        //}

    }
}